package cn.enited.devtools.apiaggregator.utils;

import cn.enited.devtools.apiaggregator.consts.ApiConst;
import cn.enited.devtools.apiaggregator.consts.CommonConst;
import cn.enited.devtools.apiaggregator.consts.RedissonLockType;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.BatchOptions;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

@Slf4j
@Component
public class RedissonUtil {

    private RedissonClient redissonClient;

    @Autowired
    public void setRedissonClient(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public RLock getLock(String name) {
        return redissonClient.getLock(name);
    }

    public RLock getLock(RedissonLockType lockType, String name) {
        switch (lockType) {
            case SPIN:
                return redissonClient.getSpinLock(name);
            case FAIR:
                return redissonClient.getFairLock(name);
            default:
                return redissonClient.getLock(name);
        }
    }

    public RReadWriteLock getReadWriteLock(String name) {
        return redissonClient.getReadWriteLock(name);
    }

    public void lock(RLock locker) {
        locker.lock();
    }

    public void lock(RLock locker, long leaseTime, TimeUnit unit) {
        locker.lock(leaseTime, unit);
    }

    public boolean tryLock(RLock locker, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        return locker.tryLock(waitTime, leaseTime, unit);
    }

    public void unlock(RLock locker) {
        locker.unlock();
    }

    public void runWithLock(String name, long leaseTime, TimeUnit unit, Runner runner) throws Throwable {
        Objects.requireNonNull(runner);
        RLock locker = getLock(name);
        try {
            lock(locker, leaseTime, unit);
            runner.run();
        } catch (Throwable e) {
            log.error("runWithLock Throwable:", e);
            throw e;
        } finally {
            locker.unlock();
        }
    }

    public void runWithLock(String name, Runner runner) throws Throwable {
        runWithLock(name, ApiConst.CACHE_LOCK.EXPIRED, ApiConst.CACHE_LOCK.EXPIRED_UNIT, runner);
    }

    public void runWithLock(String name, long leaseTime, TimeUnit unit, Runner runner,
                            Consumer<Throwable> throwableConsumer) {
        Objects.requireNonNull(throwableConsumer);
        try {
            runWithLock(name, leaseTime, unit, runner);
        } catch (Throwable e) {
            throwableConsumer.accept(e);
        }
    }

    public void runWithLock(String name, Runner runner, Consumer<Throwable> throwableConsumer) {
        runWithLock(name, ApiConst.CACHE_LOCK.EXPIRED, ApiConst.CACHE_LOCK.EXPIRED_UNIT, runner, throwableConsumer);
    }

    public <T> void consumeWithLock(String name, long leaseTime, TimeUnit unit,
                                    Consumer<T> consumer, T state) throws Throwable {
        Objects.requireNonNull(consumer);
        RLock locker = getLock(name);
        try {
            lock(locker, leaseTime, unit);
            consumer.accept(state);
        } catch (Throwable e) {
            log.error("consumeWithLock Throwable:", e);
            throw e;
        } finally {
            locker.unlock();
        }
    }

    public <T> void consumeWithLock(String name, Consumer<T> consumer, T state) throws Throwable {
        consumeWithLock(name, ApiConst.CACHE_LOCK.EXPIRED, ApiConst.CACHE_LOCK.EXPIRED_UNIT, consumer, state);
    }

    public <T> void consumeWithLock(String name, long leaseTime, TimeUnit unit,
                                    Consumer<T> consumer, T state, Consumer<Throwable> throwableHandler) {
        Objects.requireNonNull(throwableHandler);
        try {
            consumeWithLock(name, leaseTime, unit, consumer, state);
        } catch (Throwable e) {
            throwableHandler.accept(e);
        }
    }

    public <T> void consumeWithLock(String name, Consumer<T> consumer, T state, Consumer<Throwable> throwableConsumer) {
        consumeWithLock(name, ApiConst.CACHE_LOCK.EXPIRED, ApiConst.CACHE_LOCK.EXPIRED_UNIT, consumer, state, throwableConsumer);
    }

    public <T> Optional<T> supplyWithLock(String name, long leaseTime, TimeUnit unit,
                                          Supplier<T> supplier) throws Throwable {
        Objects.requireNonNull(supplier);
        RLock locker = getLock(name);
        try {
            lock(locker, leaseTime, unit);
            return Optional.ofNullable(supplier.get());
        } catch (Throwable e) {
            log.error("supplyWithLock Throwable:", e);
            throw e;
        } finally {
            locker.unlock();
        }
    }

    public <T> Optional<T> supplyWithLock(String name, Supplier<T> supplier) throws Throwable {
        return supplyWithLock(name, ApiConst.CACHE_LOCK.EXPIRED, ApiConst.CACHE_LOCK.EXPIRED_UNIT, supplier);
    }

    public <T> Optional<T> supplyWithLock(String name, long leaseTime, TimeUnit unit,
                                          Supplier<T> supplier, Consumer<Throwable> ThrowableHandler) {
        Objects.requireNonNull(ThrowableHandler);
        try {
            return supplyWithLock(name, leaseTime, unit, supplier);
        } catch (Throwable e) {
            ThrowableHandler.accept(e);
        }
        return Optional.empty();
    }

    public <T> Optional<T> supplyWithLock(String name, Supplier<T> supplier, Consumer<Throwable> ThrowableHandler) {
        return supplyWithLock(name, ApiConst.CACHE_LOCK.EXPIRED, ApiConst.CACHE_LOCK.EXPIRED_UNIT, supplier, ThrowableHandler);
    }

    public <T, R> Optional<R> getWithLock(String name, long leaseTime, TimeUnit unit,
                                          Function<T, R> func, T state) throws Throwable {
        Objects.requireNonNull(func);
        RLock locker = getLock(name);
        try {
            lock(locker, leaseTime, unit);
            return Optional.ofNullable(func.apply(state));
        } catch (Throwable e) {
            log.error("getWithLock Throwable:", e);
            throw e;
        } finally {
            locker.unlock();
        }
    }

    public <T, R> Optional<R> getWithLock(String name, Function<T, R> func, T state) throws Throwable {
        return getWithLock(name, ApiConst.CACHE_LOCK.EXPIRED, ApiConst.CACHE_LOCK.EXPIRED_UNIT, func, state);
    }

    public <T, R> Optional<R> getWithLock(String name, long leaseTime, TimeUnit unit,
                                          Function<T, R> func, T state, Consumer<Throwable> ThrowableHandler) {
        Objects.requireNonNull(ThrowableHandler);
        try {
            return getWithLock(name, leaseTime, unit, func, state);
        } catch (Throwable e) {
            ThrowableHandler.accept(e);
        }
        return Optional.empty();
    }

    public <T, R> Optional<R> getWithLock(String name, Function<T, R> func, T state, Consumer<Throwable> ThrowableHandler) {
        return getWithLock(name, ApiConst.CACHE_LOCK.EXPIRED, ApiConst.CACHE_LOCK.EXPIRED_UNIT, func, state, ThrowableHandler);
    }
}
